// SPDX-License-Identifier: MIT
// Copyright (C) 2018-present iced project and contributors

#if ENCODER
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Iced.Intel.EncoderInternal;

namespace Iced.Intel {
	/// <summary>
	/// Encodes instructions decoded by the decoder or instructions created by other code.
	/// See also BlockEncoder which can encode any number of instructions.
	/// </summary>
	public sealed class Encoder {
		// GENERATOR-BEGIN: ImmSizes
		// ⚠️This was generated by GENERATOR!🦹‍♂️
		static readonly uint[] s_immSizes = new uint[19] {
			0,// None
			1,// Size1
			2,// Size2
			4,// Size4
			8,// Size8
			3,// Size2_1
			2,// Size1_1
			4,// Size2_2
			6,// Size4_2
			1,// RipRelSize1_Target16
			1,// RipRelSize1_Target32
			1,// RipRelSize1_Target64
			2,// RipRelSize2_Target16
			2,// RipRelSize2_Target32
			2,// RipRelSize2_Target64
			4,// RipRelSize4_Target32
			4,// RipRelSize4_Target64
			1,// SizeIbReg
			1,// Size1OpCode
		};
		// GENERATOR-END: ImmSizes

		/// <summary>
		/// Disables 2-byte VEX encoding and encodes all VEX instructions with the 3-byte VEX encoding
		/// </summary>
		public bool PreventVEX2 {
			get => Internal_PreventVEX2 != 0;
			set => Internal_PreventVEX2 = value ? uint.MaxValue : 0;
		}
		internal uint Internal_PreventVEX2;

		/// <summary>
		/// Value of the <c>VEX.W</c> bit to use if it's an instruction that ignores the bit. Default is 0.
		/// </summary>
		public uint VEX_WIG {
			get => (Internal_VEX_WIG_LIG >> 7) & 1;
			set => Internal_VEX_WIG_LIG = (Internal_VEX_WIG_LIG & ~0x80U) | ((value & 1) << 7);
		}
		internal uint Internal_VEX_WIG_LIG;
		internal uint Internal_VEX_LIG;

		/// <summary>
		/// Value of the <c>VEX.L</c> bit to use if it's an instruction that ignores the bit. Default is 0.
		/// </summary>
		public uint VEX_LIG {
			get => (Internal_VEX_WIG_LIG >> 2) & 1;
			set {
				Internal_VEX_WIG_LIG = (Internal_VEX_WIG_LIG & ~4U) | ((value & 1) << 2);
				Internal_VEX_LIG = (value & 1) << 2;
			}
		}

		/// <summary>
		/// Value of the <c>EVEX.W</c> bit to use if it's an instruction that ignores the bit. Default is 0.
		/// </summary>
		public uint EVEX_WIG {
			get => Internal_EVEX_WIG >> 7;
			set => Internal_EVEX_WIG = (value & 1) << 7;
		}
		internal uint Internal_EVEX_WIG;

		/// <summary>
		/// Value of the <c>EVEX.L'L</c> bits to use if it's an instruction that ignores the bits. Default is 0.
		/// </summary>
		public uint EVEX_LIG {
			get => Internal_EVEX_LIG >> 5;
			set => Internal_EVEX_LIG = (value & 3) << 5;
		}
		internal uint Internal_EVEX_LIG;

#if MVEX
		/// <summary>
		/// Value of the <c>MVEX.W</c> bit to use if it's an instruction that ignores the bit. Default is 0.
		/// </summary>
		public uint MVEX_WIG {
			get => Internal_MVEX_WIG >> 7;
			set => Internal_MVEX_WIG = (value & 1) << 7;
		}
		internal uint Internal_MVEX_WIG;
#endif

		internal const string ERROR_ONLY_1632_BIT_MODE = "The instruction can only be used in 16/32-bit mode";
		internal const string ERROR_ONLY_64_BIT_MODE = "The instruction can only be used in 64-bit mode";

		readonly CodeWriter writer;
		readonly int bitness;
		readonly OpCodeHandler[] handlers;

		readonly uint[] immSizes;
		ulong currentRip;
		string? errorMessage;
		OpCodeHandler handler;
		uint eip;
		uint displAddr;
		uint immAddr;
		internal uint Immediate;
		// high 32 bits if it's a 64-bit immediate
		// high 32 bits if it's an IP relative immediate (jcc,call target)
		// high 32 bits if it's a 64-bit absolute address
		internal uint ImmediateHi;
		uint Displ;
		// high 32 bits if it's an IP relative mem displ (target)
		uint DisplHi;
		readonly EncoderFlags opSize16Flags;
		readonly EncoderFlags opSize32Flags;
		readonly EncoderFlags adrSize16Flags;
		readonly EncoderFlags adrSize32Flags;
		internal uint OpCode;
		internal EncoderFlags EncoderFlags;
		DisplSize DisplSize;
		internal ImmSize ImmSize;
		byte ModRM;
		byte Sib;

		/// <summary>
		/// Gets the bitness (16, 32 or 64)
		/// </summary>
		public int Bitness => bitness;

		Encoder(CodeWriter writer, int bitness) {
			Debug.Assert(bitness == 16 || bitness == 32 || bitness == 64);
			if (writer is null)
				ThrowHelper.ThrowArgumentNullException_writer();
			immSizes = s_immSizes;
			this.writer = writer;
			this.bitness = bitness;
			handlers = OpCodeHandlers.Handlers;
			handler = null!;// It's initialized by TryEncode
			opSize16Flags = bitness != 16 ? EncoderFlags.P66 : 0;
			opSize32Flags = bitness == 16 ? EncoderFlags.P66 : 0;
			adrSize16Flags = bitness != 16 ? EncoderFlags.P67 : 0;
			adrSize32Flags = bitness != 32 ? EncoderFlags.P67 : 0;
		}

		/// <summary>
		/// Creates an encoder
		/// </summary>
		/// <param name="bitness">16, 32 or 64</param>
		/// <param name="writer">Destination</param>
		/// <returns></returns>
		public static Encoder Create(int bitness, CodeWriter writer) =>
			bitness switch {
				16 or 32 or 64 => new Encoder(writer, bitness),
				_ => throw new ArgumentOutOfRangeException(nameof(bitness)),
			};

		/// <summary>
		/// Encodes an instruction and returns the size of the encoded instruction.
		/// A <see cref="EncoderException"/> is thrown if it failed to encode the instruction.
		/// </summary>
		/// <param name="instruction">Instruction to encode</param>
		/// <param name="rip">RIP of the encoded instruction</param>
		/// <returns></returns>
		public uint Encode(in Instruction instruction, ulong rip) {
			if (!TryEncode(instruction, rip, out uint result, out var errorMessage))
				ThrowEncoderException(instruction, errorMessage);
			return result;
		}

		static void ThrowEncoderException(in Instruction instruction, string errorMessage) => throw new EncoderException(errorMessage, instruction);

		/// <summary>
		/// Encodes an instruction
		/// </summary>
		/// <param name="instruction">Instruction to encode</param>
		/// <param name="rip"><c>RIP</c> of the encoded instruction</param>
		/// <param name="encodedLength">Updated with length of encoded instruction if successful</param>
		/// <param name="errorMessage">Set to the error message if we couldn't encode the instruction</param>
		/// <returns></returns>
		public bool TryEncode(in Instruction instruction, ulong rip, out uint encodedLength, [NotNullWhen(false)] out string? errorMessage) {
			currentRip = rip;
			eip = (uint)rip;
			this.errorMessage = null;
			EncoderFlags = EncoderFlags.None;
			DisplSize = DisplSize.None;
			ImmSize = ImmSize.None;
			ModRM = 0;

			var handler = handlers[(int)instruction.Code];
			this.handler = handler;
			OpCode = handler.OpCode;
			if (handler.GroupIndex >= 0) {
				Debug.Assert(EncoderFlags == 0);
				EncoderFlags = EncoderFlags.ModRM;
				ModRM = (byte)(handler.GroupIndex << 3);
			}
			if (handler.RmGroupIndex >= 0) {
				Debug.Assert(EncoderFlags == 0 || EncoderFlags == EncoderFlags.ModRM);
				EncoderFlags = EncoderFlags.ModRM;
				ModRM |= (byte)(handler.RmGroupIndex | 0xC0);
			}

			switch (handler.EncFlags3 & (EncFlags3.Bit16or32 | EncFlags3.Bit64)) {
			case EncFlags3.Bit16or32 | EncFlags3.Bit64:
				break;

			case EncFlags3.Bit16or32:
				if (bitness == 64)
					ErrorMessage = ERROR_ONLY_1632_BIT_MODE;
				break;

			case EncFlags3.Bit64:
				if (bitness != 64)
					ErrorMessage = ERROR_ONLY_64_BIT_MODE;
				break;

			default:
				throw new InvalidOperationException();
			}

			switch (handler.OpSize) {
			case CodeSize.Unknown:
				break;

			case CodeSize.Code16:
				EncoderFlags |= opSize16Flags;
				break;

			case CodeSize.Code32:
				EncoderFlags |= opSize32Flags;
				break;

			case CodeSize.Code64:
				if ((handler.EncFlags3 & EncFlags3.DefaultOpSize64) == 0)
					EncoderFlags |= EncoderFlags.W;
				break;

			default:
				throw new InvalidOperationException();
			}

			switch (handler.AddrSize) {
			case CodeSize.Unknown:
				break;

			case CodeSize.Code16:
				EncoderFlags |= adrSize16Flags;
				break;

			case CodeSize.Code32:
				EncoderFlags |= adrSize32Flags;
				break;

			case CodeSize.Code64:
				break;

			default:
				throw new InvalidOperationException();
			}

			if (!handler.IsSpecialInstr) {
				var ops = handler.Operands;
				for (int i = 0; i < ops.Length; i++)
					ops[i].Encode(this, instruction, i);

				if ((handler.EncFlags3 & EncFlags3.Fwait) != 0)
					WriteByteInternal(0x9B);

				handler.Encode(this, instruction);

				var opCode = OpCode;
				if (!handler.Is2ByteOpCode)
					WriteByteInternal(opCode);
				else {
					WriteByteInternal(opCode >> 8);
					WriteByteInternal(opCode);
				}

				if ((EncoderFlags & (EncoderFlags.ModRM | EncoderFlags.Displ)) != 0)
					WriteModRM();

				if (ImmSize != ImmSize.None)
					WriteImmediate();
			}
			else {
				Debug.Assert(handler is DeclareDataHandler || handler is ZeroBytesHandler);
				handler.Encode(this, instruction);
			}

			uint instrLen = (uint)currentRip - (uint)rip;
			if (instrLen > IcedConstants.MaxInstructionLength && !handler.IsSpecialInstr)
				ErrorMessage = $"Instruction length > {IcedConstants.MaxInstructionLength} bytes";
			errorMessage = this.errorMessage;
			if (errorMessage is not null) {
				encodedLength = 0;
				return false;
			}
			encodedLength = instrLen;
			return true;
		}

		internal string? ErrorMessage {
			set {
				if (errorMessage is null)
					errorMessage = value;
			}
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		internal bool Verify(int operand, OpKind expected, OpKind actual) {
			if (expected == actual)
				return true;
			ErrorMessage = $"Operand {operand}: Expected: {expected}, actual: {actual}";
			return false;
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		internal bool Verify(int operand, Register expected, Register actual) {
			if (expected == actual)
				return true;
			ErrorMessage = $"Operand {operand}: Expected: {expected}, actual: {actual}";
			return false;
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		internal bool Verify(int operand, Register register, Register regLo, Register regHi) {
			if (bitness != 64 && regHi > regLo + 7)
				regHi = regLo + 7;
			if (regLo <= register && register <= regHi)
				return true;
			ErrorMessage = $"Operand {operand}: Register {register} is not between {regLo} and {regHi} (inclusive)";
			return false;
		}

		internal void AddBranch(OpKind opKind, int immSize, in Instruction instruction, int operand) {
			if (!Verify(operand, opKind, instruction.GetOpKind(operand)))
				return;

			ulong target;
			switch (immSize) {
			case 1:
				switch (opKind) {
				case OpKind.NearBranch16:
					EncoderFlags |= opSize16Flags;
					ImmSize = ImmSize.RipRelSize1_Target16;
					Immediate = instruction.NearBranch16;
					break;

				case OpKind.NearBranch32:
					EncoderFlags |= opSize32Flags;
					ImmSize = ImmSize.RipRelSize1_Target32;
					Immediate = instruction.NearBranch32;
					break;

				case OpKind.NearBranch64:
					ImmSize = ImmSize.RipRelSize1_Target64;
					target = instruction.NearBranch64;
					Immediate = (uint)target;
					ImmediateHi = (uint)(target >> 32);
					break;

				default:
					throw new InvalidOperationException();
				}
				break;

			case 2:
				switch (opKind) {
				case OpKind.NearBranch16:
					EncoderFlags |= opSize16Flags;
					ImmSize = ImmSize.RipRelSize2_Target16;
					Immediate = instruction.NearBranch16;
					break;

				default:
					throw new InvalidOperationException();
				}
				break;

			case 4:
				switch (opKind) {
				case OpKind.NearBranch32:
					EncoderFlags |= opSize32Flags;
					ImmSize = ImmSize.RipRelSize4_Target32;
					Immediate = instruction.NearBranch32;
					break;

				case OpKind.NearBranch64:
					ImmSize = ImmSize.RipRelSize4_Target64;
					target = instruction.NearBranch64;
					Immediate = (uint)target;
					ImmediateHi = (uint)(target >> 32);
					break;

				default:
					throw new InvalidOperationException();
				}
				break;

			default:
				throw new InvalidOperationException();
			}
		}

		internal void AddBranchX(int immSize, in Instruction instruction, int operand) {
			if (bitness == 64) {
				if (!Verify(operand, OpKind.NearBranch64, instruction.GetOpKind(operand)))
					return;

				var target = instruction.NearBranch64;
				switch (immSize) {
				case 2:
					EncoderFlags |= EncoderFlags.P66;
					ImmSize = ImmSize.RipRelSize2_Target64;
					Immediate = (uint)target;
					ImmediateHi = (uint)(target >> 32);
					break;

				case 4:
					ImmSize = ImmSize.RipRelSize4_Target64;
					Immediate = (uint)target;
					ImmediateHi = (uint)(target >> 32);
					break;

				default:
					throw new InvalidOperationException();
				}
			}
			else {
				Debug.Assert(bitness == 16 || bitness == 32);
				if (!Verify(operand, OpKind.NearBranch32, instruction.GetOpKind(operand)))
					return;

				switch (immSize) {
				case 2:
					Static.Assert((int)EncoderFlags.P66 == 0x80 ? 0 : -1);
					EncoderFlags |= (EncoderFlags)((bitness & 0x20) << 2);
					ImmSize = ImmSize.RipRelSize2_Target32;
					Immediate = instruction.NearBranch32;
					break;

				case 4:
					Static.Assert((int)EncoderFlags.P66 == 0x80 ? 0 : -1);
					EncoderFlags |= (EncoderFlags)((bitness & 0x10) << 3);
					ImmSize = ImmSize.RipRelSize4_Target32;
					Immediate = instruction.NearBranch32;
					break;

				case 8:
				default:
					throw new InvalidOperationException();
				}
			}
		}

		internal void AddBranchDisp(int displSize, in Instruction instruction, int operand) {
			Debug.Assert(displSize == 2 || displSize == 4);
			OpKind opKind;
			switch (displSize) {
			case 2:
				opKind = OpKind.NearBranch16;
				ImmSize = ImmSize.Size2;
				Immediate = instruction.NearBranch16;
				break;

			case 4:
				opKind = OpKind.NearBranch32;
				ImmSize = ImmSize.Size4;
				Immediate = instruction.NearBranch32;
				break;

			default:
				throw new InvalidOperationException();
			}
			if (!Verify(operand, opKind, instruction.GetOpKind(operand)))
				return;
		}

		internal void AddFarBranch(in Instruction instruction, int operand, int size) {
			if (size == 2) {
				if (!Verify(operand, OpKind.FarBranch16, instruction.GetOpKind(operand)))
					return;
				ImmSize = ImmSize.Size2_2;
				Immediate = instruction.FarBranch16;
				ImmediateHi = instruction.FarBranchSelector;
			}
			else {
				Debug.Assert(size == 4);
				if (!Verify(operand, OpKind.FarBranch32, instruction.GetOpKind(operand)))
					return;
				ImmSize = ImmSize.Size4_2;
				Immediate = instruction.FarBranch32;
				ImmediateHi = instruction.FarBranchSelector;
			}
			if (bitness != size * 8)
				EncoderFlags |= EncoderFlags.P66;
		}

		internal void SetAddrSize(int regSize) {
			Debug.Assert(regSize == 2 || regSize == 4 || regSize == 8);
			if (bitness == 64) {
				if (regSize == 2) {
					ErrorMessage = $"Invalid register size: {regSize * 8}, must be 32-bit or 64-bit";
				}
				else if (regSize == 4)
					EncoderFlags |= EncoderFlags.P67;
			}
			else {
				if (regSize == 8) {
					ErrorMessage = $"Invalid register size: {regSize * 8}, must be 16-bit or 32-bit";
				}
				else if (bitness == 16) {
					if (regSize == 4)
						EncoderFlags |= EncoderFlags.P67;
				}
				else {
					Debug.Assert(bitness == 32);
					if (regSize == 2)
						EncoderFlags |= EncoderFlags.P67;
				}
			}
		}

		internal void AddAbsMem(in Instruction instruction, int operand) {
			EncoderFlags |= EncoderFlags.Displ;
			var opKind = instruction.GetOpKind(operand);
			if (opKind == OpKind.Memory) {
				if (instruction.MemoryBase != Register.None || instruction.MemoryIndex != Register.None) {
					ErrorMessage = $"Operand {operand}: Absolute addresses can't have base and/or index regs";
					return;
				}
				if (instruction.MemoryIndexScale != 1) {
					ErrorMessage = $"Operand {operand}: Absolute addresses must have scale == *1";
					return;
				}
				switch (instruction.MemoryDisplSize) {
				case 2:
					if (bitness == 64) {
						ErrorMessage = $"Operand {operand}: 16-bit abs addresses can't be used in 64-bit mode";
						return;
					}
					if (bitness == 32)
						EncoderFlags |= EncoderFlags.P67;
					DisplSize = DisplSize.Size2;
					if (instruction.MemoryDisplacement64 > ushort.MaxValue) {
						ErrorMessage = $"Operand {operand}: Displacement must fit in a ushort";
						return;
					}
					Displ = instruction.MemoryDisplacement32;
					break;
				case 4:
					EncoderFlags |= adrSize32Flags;
					DisplSize = DisplSize.Size4;
					if (instruction.MemoryDisplacement64 > uint.MaxValue) {
						ErrorMessage = $"Operand {operand}: Displacement must fit in a uint";
						return;
					}
					Displ = instruction.MemoryDisplacement32;
					break;
				case 8:
					if (bitness != 64) {
						ErrorMessage = $"Operand {operand}: 64-bit abs address is only available in 64-bit mode";
						return;
					}
					DisplSize = DisplSize.Size8;
					ulong addr = instruction.MemoryDisplacement64;
					Displ = (uint)addr;
					DisplHi = (uint)(addr >> 32);
					break;
				default:
					ErrorMessage = $"Operand {operand}: {nameof(Instruction)}.{nameof(Instruction.MemoryDisplSize)} must be initialized to 2 (16-bit), 4 (32-bit) or 8 (64-bit)";
					break;
				}
			}
			else
				ErrorMessage = $"Operand {operand}: Expected OpKind {nameof(OpKind.Memory)}, actual: {opKind}";
		}

		internal void AddModRMRegister(in Instruction instruction, int operand, Register regLo, Register regHi) {
			if (!Verify(operand, OpKind.Register, instruction.GetOpKind(operand)))
				return;
			var reg = instruction.GetOpRegister(operand);
			if (!Verify(operand, reg, regLo, regHi))
				return;
			uint regNum = (uint)(reg - regLo);
			if (regLo == Register.AL) {
				if (reg >= Register.SPL) {
					regNum -= 4;
					EncoderFlags |= EncoderFlags.REX;
				}
				else if (reg >= Register.AH)
					EncoderFlags |= EncoderFlags.HighLegacy8BitRegs;
			}
			Debug.Assert(regNum <= 31);
			ModRM |= (byte)((regNum & 7) << 3);
			EncoderFlags |= EncoderFlags.ModRM;
			Static.Assert((int)EncoderFlags.R == 4 ? 0 : -1);
			EncoderFlags |= (EncoderFlags)((regNum & 8) >> 1);
			Static.Assert((int)EncoderFlags.R2 == 0x200 ? 0 : -1);
			EncoderFlags |= (EncoderFlags)((regNum & 0x10) << (9 - 4));
		}

		internal void AddReg(in Instruction instruction, int operand, Register regLo, Register regHi) {
			if (!Verify(operand, OpKind.Register, instruction.GetOpKind(operand)))
				return;
			var reg = instruction.GetOpRegister(operand);
			if (!Verify(operand, reg, regLo, regHi))
				return;
			uint regNum = (uint)(reg - regLo);
			if (regLo == Register.AL) {
				if (reg >= Register.SPL) {
					regNum -= 4;
					EncoderFlags |= EncoderFlags.REX;
				}
				else if (reg >= Register.AH)
					EncoderFlags |= EncoderFlags.HighLegacy8BitRegs;
			}
			Debug.Assert(regNum <= 15);
			OpCode |= regNum & 7;
			Static.Assert((int)EncoderFlags.B == 1 ? 0 : -1);
			Debug.Assert(regNum <= 15);
			EncoderFlags |= (EncoderFlags)(regNum >> 3);// regNum <= 15, so no need to mask out anything
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		internal void AddRegOrMem(in Instruction instruction, int operand, Register regLo, Register regHi, bool allowMemOp, bool allowRegOp) =>
			AddRegOrMem(instruction, operand, regLo, regHi, Register.None, Register.None, allowMemOp, allowRegOp);

		internal void AddRegOrMem(in Instruction instruction, int operand, Register regLo, Register regHi, Register vsibIndexRegLo, Register vsibIndexRegHi, bool allowMemOp, bool allowRegOp) {
			var opKind = instruction.GetOpKind(operand);
			EncoderFlags |= EncoderFlags.ModRM;
			if (opKind == OpKind.Register) {
				if (!allowRegOp) {
					ErrorMessage = $"Operand {operand}: register operand is not allowed";
					return;
				}
				var reg = instruction.GetOpRegister(operand);
				if (!Verify(operand, reg, regLo, regHi))
					return;
				uint regNum = (uint)(reg - regLo);
				if (regLo == Register.AL) {
					if (reg >= Register.R8L)
						regNum -= 4;
					else if (reg >= Register.SPL) {
						regNum -= 4;
						EncoderFlags |= EncoderFlags.REX;
					}
					else if (reg >= Register.AH)
						EncoderFlags |= EncoderFlags.HighLegacy8BitRegs;
				}
				ModRM |= (byte)(regNum & 7);
				ModRM |= 0xC0;
				Static.Assert((int)EncoderFlags.B == 1 ? 0 : -1);
				Static.Assert((int)EncoderFlags.X == 2 ? 0 : -1);
				EncoderFlags |= (EncoderFlags)((regNum >> 3) & 3);
				Debug.Assert(regNum <= 31);
			}
			else if (opKind == OpKind.Memory) {
				if (!allowMemOp) {
					ErrorMessage = $"Operand {operand}: memory operand is not allowed";
					return;
				}
				if (instruction.MemorySize.IsBroadcast())
					EncoderFlags |= EncoderFlags.Broadcast;

				var codeSize = instruction.CodeSize;
				if (codeSize == CodeSize.Unknown) {
					if (bitness == 64)
						codeSize = CodeSize.Code64;
					else if (bitness == 32)
						codeSize = CodeSize.Code32;
					else {
						Debug.Assert(bitness == 16);
						codeSize = CodeSize.Code16;
					}
				}
				int addrSize = InstructionUtils.GetAddressSizeInBytes(instruction.MemoryBase, instruction.MemoryIndex, instruction.MemoryDisplSize, codeSize) * 8;
				if (addrSize != bitness)
					EncoderFlags |= EncoderFlags.P67;
				if ((EncoderFlags & EncoderFlags.RegIsMemory) != 0) {
					var regSize = GetRegisterOpSize(instruction);
					if (regSize != addrSize) {
						ErrorMessage = $"Operand {operand}: Register operand size must equal memory addressing mode (16/32/64)";
						return;
					}
				}
				if (addrSize == 16) {
					if (vsibIndexRegLo != Register.None) {
						ErrorMessage = $"Operand {operand}: VSIB operands can't use 16-bit addressing. It must be 32-bit or 64-bit addressing";
						return;
					}
					AddMemOp16(instruction, operand);
				}
				else
					AddMemOp(instruction, operand, addrSize, vsibIndexRegLo, vsibIndexRegHi);
			}
			else
				ErrorMessage = $"Operand {operand}: Expected a register or memory operand, but opKind is {opKind}";
		}

		static int GetRegisterOpSize(in Instruction instruction) {
			Debug.Assert(instruction.Op0Kind == OpKind.Register);
			if (instruction.Op0Kind == OpKind.Register) {
				var reg = instruction.Op0Register;
				if (reg.IsGPR64())
					return 64;
				if (reg.IsGPR32())
					return 32;
				if (reg.IsGPR16())
					return 16;
			}
			return 0;
		}

		bool TryConvertToDisp8N(in Instruction instruction, int displ, out sbyte compressedValue) {
			var tryConvertToDisp8N = handler.TryConvertToDisp8N;
			if (tryConvertToDisp8N is not null)
				return tryConvertToDisp8N(this, handler, instruction, displ, out compressedValue);
			if (sbyte.MinValue <= displ && displ <= sbyte.MaxValue) {
				compressedValue = (sbyte)displ;
				return true;
			}
			compressedValue = 0;
			return false;
		}

		void AddMemOp16(in Instruction instruction, int operand) {
			if (bitness == 64) {
				ErrorMessage = $"Operand {operand}: 16-bit addressing can't be used by 64-bit code";
				return;
			}
			var baseReg = instruction.MemoryBase;
			var indexReg = instruction.MemoryIndex;
			var displSize = instruction.MemoryDisplSize;
			if (baseReg == Register.BX && indexReg == Register.SI) {
				// Nothing
			}
			else if (baseReg == Register.BX && indexReg == Register.DI)
				ModRM |= 1;
			else if (baseReg == Register.BP && indexReg == Register.SI)
				ModRM |= 2;
			else if (baseReg == Register.BP && indexReg == Register.DI)
				ModRM |= 3;
			else if (baseReg == Register.SI && indexReg == Register.None)
				ModRM |= 4;
			else if (baseReg == Register.DI && indexReg == Register.None)
				ModRM |= 5;
			else if (baseReg == Register.BP && indexReg == Register.None)
				ModRM |= 6;
			else if (baseReg == Register.BX && indexReg == Register.None)
				ModRM |= 7;
			else if (baseReg == Register.None && indexReg == Register.None) {
				ModRM |= 6;
				DisplSize = DisplSize.Size2;
				if (instruction.MemoryDisplacement64 > ushort.MaxValue) {
					ErrorMessage = $"Operand {operand}: Displacement must fit in a ushort";
					return;
				}
				Displ = instruction.MemoryDisplacement32;
			}
			else {
				ErrorMessage = $"Operand {operand}: Invalid 16-bit base + index registers: base={baseReg}, index={indexReg}";
				return;
			}

			if (baseReg != Register.None || indexReg != Register.None) {
				if ((long)instruction.MemoryDisplacement64 < short.MinValue || (long)instruction.MemoryDisplacement64 > ushort.MaxValue) {
					ErrorMessage = $"Operand {operand}: Displacement must fit in a short or a ushort";
					return;
				}
				Displ = instruction.MemoryDisplacement32;
				// [bp] => [bp+00]
				if (displSize == 0 && baseReg == Register.BP && indexReg == Register.None) {
					displSize = 1;
					if (Displ != 0) {
						ErrorMessage = $"Operand {operand}: Displacement must be 0 if displSize == 0";
						return;
					}
				}
				if (displSize == 1) {
					if (TryConvertToDisp8N(instruction, (short)Displ, out sbyte compressedValue))
						Displ = (uint)compressedValue;
					else
						displSize = 2;
				}
				if (displSize == 0) {
					if (Displ != 0) {
						ErrorMessage = $"Operand {operand}: Displacement must be 0 if displSize == 0";
						return;
					}
				}
				else if (displSize == 1) {
					// This if check should never be true when we're here
					if ((int)Displ < sbyte.MinValue || (int)Displ > sbyte.MaxValue) {
						ErrorMessage = $"Operand {operand}: Displacement must fit in an sbyte";
						return;
					}
					ModRM |= 0x40;
					DisplSize = DisplSize.Size1;
				}
				else if (displSize == 2) {
					ModRM |= 0x80;
					DisplSize = DisplSize.Size2;
				}
				else {
					ErrorMessage = $"Operand {operand}: Invalid displacement size: {displSize}, must be 0, 1, or 2";
					return;
				}
			}
		}

		void AddMemOp(in Instruction instruction, int operand, int addrSize, Register vsibIndexRegLo, Register vsibIndexRegHi) {
			Debug.Assert(addrSize == 32 || addrSize == 64);
			if (bitness != 64 && addrSize == 64) {
				ErrorMessage = $"Operand {operand}: 64-bit addressing can only be used in 64-bit mode";
				return;
			}

			var baseReg = instruction.MemoryBase;
			var indexReg = instruction.MemoryIndex;
			var displSize = instruction.MemoryDisplSize;

			Register baseRegLo, baseRegHi;
			Register indexRegLo, indexRegHi;
			if (addrSize == 64) {
				baseRegLo = Register.RAX;
				baseRegHi = Register.R15;
			}
			else {
				Debug.Assert(addrSize == 32);
				baseRegLo = Register.EAX;
				baseRegHi = Register.R15D;
			}
			if (vsibIndexRegLo != Register.None) {
				indexRegLo = vsibIndexRegLo;
				indexRegHi = vsibIndexRegHi;
			}
			else {
				indexRegLo = baseRegLo;
				indexRegHi = baseRegHi;
			}
			if (baseReg != Register.None && baseReg != Register.RIP && baseReg != Register.EIP && !Verify(operand, baseReg, baseRegLo, baseRegHi))
				return;
			if (indexReg != Register.None && !Verify(operand, indexReg, indexRegLo, indexRegHi))
				return;

			if (displSize != 0 && displSize != 1 && displSize != 4 && displSize != 8) {
				ErrorMessage = $"Operand {operand}: Invalid displ size: {displSize}, must be 0, 1, 4, 8";
				return;
			}
			if (baseReg == Register.RIP || baseReg == Register.EIP) {
				if (indexReg != Register.None) {
					ErrorMessage = $"Operand {operand}: RIP relative addressing can't use an index register";
					return;
				}
				if (instruction.InternalMemoryIndexScale != 0) {
					ErrorMessage = $"Operand {operand}: RIP relative addressing must use scale *1";
					return;
				}
				if (bitness != 64) {
					ErrorMessage = $"Operand {operand}: RIP/EIP relative addressing is only available in 64-bit mode";
					return;
				}
				if ((EncoderFlags & EncoderFlags.MustUseSib) != 0) {
					ErrorMessage = $"Operand {operand}: RIP/EIP relative addressing isn't supported";
					return;
				}
				ModRM |= 5;
				ulong target = instruction.MemoryDisplacement64;
				if (baseReg == Register.RIP) {
					DisplSize = DisplSize.RipRelSize4_Target64;
					Displ = (uint)target;
					DisplHi = (uint)(target >> 32);
				}
				else {
					DisplSize = DisplSize.RipRelSize4_Target32;
					if (target > uint.MaxValue) {
						ErrorMessage = $"Operand {operand}: Target address doesn't fit in 32 bits: 0x{target:X}";
						return;
					}
					Displ = (uint)target;
				}
				return;
			}
			var scale = instruction.InternalMemoryIndexScale;
			Displ = instruction.MemoryDisplacement32;
			if (addrSize == 64) {
				if ((long)instruction.MemoryDisplacement64 < int.MinValue || (long)instruction.MemoryDisplacement64 > int.MaxValue) {
					ErrorMessage = $"Operand {operand}: Displacement must fit in an int";
					return;
				}
			}
			else {
				Debug.Assert(addrSize == 32);
				if ((long)instruction.MemoryDisplacement64 < int.MinValue || (long)instruction.MemoryDisplacement64 > uint.MaxValue) {
					ErrorMessage = $"Operand {operand}: Displacement must fit in an int or a uint";
					return;
				}
			}
			if (baseReg == Register.None && indexReg == Register.None) {
				if (vsibIndexRegLo != Register.None) {
					ErrorMessage = $"Operand {operand}: VSIB addressing can't use an offset-only address";
					return;
				}
				if (bitness == 64 || scale != 0 || (EncoderFlags & EncoderFlags.MustUseSib) != 0) {
					ModRM |= 4;
					DisplSize = DisplSize.Size4;
					EncoderFlags |= EncoderFlags.Sib;
					Sib = (byte)(0x25 | (scale << 6));
					return;
				}
				else {
					ModRM |= 5;
					DisplSize = DisplSize.Size4;
					return;
				}
			}

			int baseNum = baseReg == Register.None ? -1 : baseReg - baseRegLo;
			int indexNum = indexReg == Register.None ? -1 : indexReg - indexRegLo;

			// [ebp]/[ebp+index*scale] => [ebp+00]/[ebp+index*scale+00]
			if (displSize == 0 && (baseNum & 7) == 5) {
				displSize = 1;
				if (Displ != 0) {
					ErrorMessage = $"Operand {operand}: Displacement must be 0 if displSize == 0";
					return;
				}
			}

			if (displSize == 1) {
				if (TryConvertToDisp8N(instruction, (int)Displ, out sbyte compressedValue))
					Displ = (uint)compressedValue;
				else
					displSize = addrSize / 8;
			}

			if (baseReg == Register.None) {
				// Tested earlier in the method
				Debug.Assert(indexReg != Register.None);
				DisplSize = DisplSize.Size4;
			}
			else if (displSize == 1) {
				// This if check should never be true when we're here
				if ((int)Displ < sbyte.MinValue || (int)Displ > sbyte.MaxValue) {
					ErrorMessage = $"Operand {operand}: Displacement must fit in an sbyte";
					return;
				}
				ModRM |= 0x40;
				DisplSize = DisplSize.Size1;
			}
			else if (displSize == addrSize / 8) {
				ModRM |= 0x80;
				DisplSize = DisplSize.Size4;
			}
			else if (displSize == 0) {
				if (Displ != 0) {
					ErrorMessage = $"Operand {operand}: Displacement must be 0 if displSize == 0";
					return;
				}
			}
			else {
				ErrorMessage = $"Operand {operand}: Invalid {nameof(Instruction.MemoryDisplSize)} value";
				return;
			}

			if (indexReg == Register.None && (baseNum & 7) != 4 && scale == 0 && (EncoderFlags & EncoderFlags.MustUseSib) == 0) {
				// Tested earlier in the method
				Debug.Assert(baseReg != Register.None);
				ModRM |= (byte)(baseNum & 7);
			}
			else {
				EncoderFlags |= EncoderFlags.Sib;
				Sib = (byte)(scale << 6);
				ModRM |= 4;
				if (indexReg == Register.RSP || indexReg == Register.ESP) {
					ErrorMessage = $"Operand {operand}: ESP/RSP can't be used as an index register";
					return;
				}
				if (baseNum < 0)
					Sib |= 5;
				else
					Sib |= (byte)(baseNum & 7);
				if (indexNum < 0)
					Sib |= 0x20;
				else
					Sib |= (byte)((indexNum & 7) << 3);
			}

			if (baseNum >= 0) {
				Static.Assert((int)EncoderFlags.B == 1 ? 0 : -1);
				Debug.Assert(baseNum <= 15);// No '& 1' required below
				EncoderFlags |= (EncoderFlags)(baseNum >> 3);
			}
			if (indexNum >= 0) {
				Static.Assert((int)EncoderFlags.X == 2 ? 0 : -1);
				EncoderFlags |= (EncoderFlags)((indexNum >> 2) & 2);
				EncoderFlags |= (EncoderFlags)((indexNum & 0x10) << (int)EncoderFlags.VvvvvShift);
				Debug.Assert(indexNum <= 31);
			}
		}

#if HAS_SPAN
		static ReadOnlySpan<byte> SegmentOverrides =>// Property
#else
		static readonly byte[] SegmentOverrides =// Field
#endif
			new byte[6] { 0x26, 0x2E, 0x36, 0x3E, 0x64, 0x65 };

		internal void WritePrefixes(in Instruction instruction, bool canWriteF3 = true) {
			Debug.Assert(!handler.IsSpecialInstr);
			var seg = instruction.SegmentPrefix;
			if (seg != Register.None) {
				Debug.Assert((uint)(seg - Register.ES) < (uint)SegmentOverrides.Length);
				WriteByteInternal(SegmentOverrides[seg - Register.ES]);
			}
			if ((EncoderFlags & EncoderFlags.PF0) != 0 || instruction.HasLockPrefix)
				WriteByteInternal(0xF0);
			if ((EncoderFlags & EncoderFlags.P66) != 0)
				WriteByteInternal(0x66);
			if ((EncoderFlags & EncoderFlags.P67) != 0)
				WriteByteInternal(0x67);
			if (canWriteF3 && instruction.HasRepePrefix)
				WriteByteInternal(0xF3);
			if (instruction.HasRepnePrefix)
				WriteByteInternal(0xF2);
		}

		void WriteModRM() {
			Debug.Assert(!handler.IsSpecialInstr);
			Debug.Assert((EncoderFlags & (EncoderFlags.ModRM | EncoderFlags.Displ)) != 0);
			if ((EncoderFlags & EncoderFlags.ModRM) != 0) {
				WriteByteInternal(ModRM);
				if ((EncoderFlags & EncoderFlags.Sib) != 0)
					WriteByteInternal(Sib);
			}

			uint diff4;
			displAddr = (uint)currentRip;
			switch (DisplSize) {
			case DisplSize.None:
				break;

			case DisplSize.Size1:
				WriteByteInternal(Displ);
				break;

			case DisplSize.Size2:
				diff4 = Displ;
				WriteByteInternal(diff4);
				WriteByteInternal(diff4 >> 8);
				break;

			case DisplSize.Size4:
				diff4 = Displ;
				WriteByteInternal(diff4);
				WriteByteInternal(diff4 >> 8);
				WriteByteInternal(diff4 >> 16);
				WriteByteInternal(diff4 >> 24);
				break;

			case DisplSize.Size8:
				diff4 = Displ;
				WriteByteInternal(diff4);
				WriteByteInternal(diff4 >> 8);
				WriteByteInternal(diff4 >> 16);
				WriteByteInternal(diff4 >> 24);
				diff4 = DisplHi;
				WriteByteInternal(diff4);
				WriteByteInternal(diff4 >> 8);
				WriteByteInternal(diff4 >> 16);
				WriteByteInternal(diff4 >> 24);
				break;

			case DisplSize.RipRelSize4_Target32:
				uint eip = (uint)currentRip + 4 + immSizes[(int)ImmSize];
				diff4 = Displ - eip;
				WriteByteInternal(diff4);
				WriteByteInternal(diff4 >> 8);
				WriteByteInternal(diff4 >> 16);
				WriteByteInternal(diff4 >> 24);
				break;

			case DisplSize.RipRelSize4_Target64:
				ulong rip = currentRip + 4 + immSizes[(int)ImmSize];
				long diff8 = (long)(((ulong)DisplHi << 32) | (ulong)Displ) - (long)rip;
				if (diff8 < int.MinValue || diff8 > int.MaxValue)
					ErrorMessage = $"RIP relative distance is too far away: NextIP: 0x{rip:X16} target: 0x{DisplHi:X8}{Displ:X8}, diff = {diff8}, diff must fit in an Int32";
				diff4 = (uint)diff8;
				WriteByteInternal(diff4);
				WriteByteInternal(diff4 >> 8);
				WriteByteInternal(diff4 >> 16);
				WriteByteInternal(diff4 >> 24);
				break;

			default:
				throw new InvalidOperationException();
			}
		}

		void WriteImmediate() {
			Debug.Assert(!handler.IsSpecialInstr);
			ushort ip;
			uint eip;
			ulong rip;
			short diff2;
			int diff4;
			long diff8;
			uint value;
			immAddr = (uint)currentRip;
			switch (ImmSize) {
			case ImmSize.None:
				break;

			case ImmSize.Size1:
			case ImmSize.SizeIbReg:
			case ImmSize.Size1OpCode:
				WriteByteInternal(Immediate);
				break;

			case ImmSize.Size2:
				value = Immediate;
				WriteByteInternal(value);
				WriteByteInternal(value >> 8);
				break;

			case ImmSize.Size4:
				value = Immediate;
				WriteByteInternal(value);
				WriteByteInternal(value >> 8);
				WriteByteInternal(value >> 16);
				WriteByteInternal(value >> 24);
				break;

			case ImmSize.Size8:
				value = Immediate;
				WriteByteInternal(value);
				WriteByteInternal(value >> 8);
				WriteByteInternal(value >> 16);
				WriteByteInternal(value >> 24);
				value = ImmediateHi;
				WriteByteInternal(value);
				WriteByteInternal(value >> 8);
				WriteByteInternal(value >> 16);
				WriteByteInternal(value >> 24);
				break;

			case ImmSize.Size2_1:
				value = Immediate;
				WriteByteInternal(value);
				WriteByteInternal(value >> 8);
				WriteByteInternal(ImmediateHi);
				break;

			case ImmSize.Size1_1:
				WriteByteInternal(Immediate);
				WriteByteInternal(ImmediateHi);
				break;

			case ImmSize.Size2_2:
				value = Immediate;
				WriteByteInternal(value);
				WriteByteInternal(value >> 8);
				value = ImmediateHi;
				WriteByteInternal(value);
				WriteByteInternal(value >> 8);
				break;

			case ImmSize.Size4_2:
				value = Immediate;
				WriteByteInternal(value);
				WriteByteInternal(value >> 8);
				WriteByteInternal(value >> 16);
				WriteByteInternal(value >> 24);
				value = ImmediateHi;
				WriteByteInternal(value);
				WriteByteInternal(value >> 8);
				break;

			case ImmSize.RipRelSize1_Target16:
				ip = (ushort)((uint)currentRip + 1);
				diff2 = (short)((short)Immediate - (short)ip);
				if (diff2 < sbyte.MinValue || diff2 > sbyte.MaxValue)
					ErrorMessage = $"Branch distance is too far away: NextIP: 0x{ip:X4} target: 0x{(ushort)Immediate:X4}, diff = {diff2}, diff must fit in an Int8";
				WriteByteInternal((uint)diff2);
				break;

			case ImmSize.RipRelSize1_Target32:
				eip = (uint)currentRip + 1;
				diff4 = (int)Immediate - (int)eip;
				if (diff4 < sbyte.MinValue || diff4 > sbyte.MaxValue)
					ErrorMessage = $"Branch distance is too far away: NextIP: 0x{eip:X8} target: 0x{Immediate:X8}, diff = {diff4}, diff must fit in an Int8";
				WriteByteInternal((uint)diff4);
				break;

			case ImmSize.RipRelSize1_Target64:
				rip = currentRip + 1;
				diff8 = (long)(((ulong)ImmediateHi << 32) | (ulong)Immediate) - (long)rip;
				if (diff8 < sbyte.MinValue || diff8 > sbyte.MaxValue)
					ErrorMessage = $"Branch distance is too far away: NextIP: 0x{rip:X16} target: 0x{ImmediateHi:X8}{Immediate:X8}, diff = {diff8}, diff must fit in an Int8";
				WriteByteInternal((uint)diff8);
				break;

			case ImmSize.RipRelSize2_Target16:
				eip = (uint)currentRip + 2;
				value = Immediate - eip;
				WriteByteInternal(value);
				WriteByteInternal(value >> 8);
				break;

			case ImmSize.RipRelSize2_Target32:
				eip = (uint)currentRip + 2;
				diff4 = (int)(Immediate - eip);
				if (diff4 < short.MinValue || diff4 > short.MaxValue)
					ErrorMessage = $"Branch distance is too far away: NextIP: 0x{eip:X8} target: 0x{Immediate:X8}, diff = {diff4}, diff must fit in an Int16";
				value = (uint)diff4;
				WriteByteInternal(value);
				WriteByteInternal(value >> 8);
				break;

			case ImmSize.RipRelSize2_Target64:
				rip = currentRip + 2;
				diff8 = (long)(((ulong)ImmediateHi << 32) | (ulong)Immediate) - (long)rip;
				if (diff8 < short.MinValue || diff8 > short.MaxValue)
					ErrorMessage = $"Branch distance is too far away: NextIP: 0x{rip:X16} target: 0x{ImmediateHi:X8}{Immediate:X8}, diff = {diff8}, diff must fit in an Int16";
				value = (uint)diff8;
				WriteByteInternal(value);
				WriteByteInternal(value >> 8);
				break;

			case ImmSize.RipRelSize4_Target32:
				eip = (uint)currentRip + 4;
				value = Immediate - eip;
				WriteByteInternal(value);
				WriteByteInternal(value >> 8);
				WriteByteInternal(value >> 16);
				WriteByteInternal(value >> 24);
				break;

			case ImmSize.RipRelSize4_Target64:
				rip = currentRip + 4;
				diff8 = (long)(((ulong)ImmediateHi << 32) | (ulong)Immediate) - (long)rip;
				if (diff8 < int.MinValue || diff8 > int.MaxValue)
					ErrorMessage = $"Branch distance is too far away: NextIP: 0x{rip:X16} target: 0x{ImmediateHi:X8}{Immediate:X8}, diff = {diff8}, diff must fit in an Int32";
				value = (uint)diff8;
				WriteByteInternal(value);
				WriteByteInternal(value >> 8);
				WriteByteInternal(value >> 16);
				WriteByteInternal(value >> 24);
				break;

			default:
				throw new InvalidOperationException();
			}
		}

		/// <summary>
		/// Writes a byte to the output buffer
		/// </summary>
		/// <param name="value">Value to write</param>
		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		public void WriteByte(byte value) => WriteByteInternal(value);

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		internal void WriteByteInternal(uint value) {
			writer.WriteByte((byte)value);
			currentRip++;
		}

		/// <summary>
		/// Gets the offsets of the constants (memory displacement and immediate) in the encoded instruction.
		/// The caller can use this information to add relocations if needed.
		/// </summary>
		/// <returns></returns>
		public ConstantOffsets GetConstantOffsets() {
			ConstantOffsets constantOffsets = default;

			switch (DisplSize) {
			case DisplSize.None:
				break;

			case DisplSize.Size1:
				constantOffsets.DisplacementSize = 1;
				constantOffsets.DisplacementOffset = (byte)(displAddr - eip);
				break;

			case DisplSize.Size2:
				constantOffsets.DisplacementSize = 2;
				constantOffsets.DisplacementOffset = (byte)(displAddr - eip);
				break;

			case DisplSize.Size4:
			case DisplSize.RipRelSize4_Target32:
			case DisplSize.RipRelSize4_Target64:
				constantOffsets.DisplacementSize = 4;
				constantOffsets.DisplacementOffset = (byte)(displAddr - eip);
				break;

			case DisplSize.Size8:
				constantOffsets.DisplacementSize = 8;
				constantOffsets.DisplacementOffset = (byte)(displAddr - eip);
				break;

			default:
				throw new InvalidOperationException();
			}

			switch (ImmSize) {
			case ImmSize.None:
			case ImmSize.SizeIbReg:
			case ImmSize.Size1OpCode:
				break;

			case ImmSize.Size1:
			case ImmSize.RipRelSize1_Target16:
			case ImmSize.RipRelSize1_Target32:
			case ImmSize.RipRelSize1_Target64:
				constantOffsets.ImmediateSize = 1;
				constantOffsets.ImmediateOffset = (byte)(immAddr - eip);
				break;

			case ImmSize.Size1_1:
				constantOffsets.ImmediateSize = 1;
				constantOffsets.ImmediateOffset = (byte)(immAddr - eip);
				constantOffsets.ImmediateSize2 = 1;
				constantOffsets.ImmediateOffset2 = (byte)(immAddr - eip + 1);
				break;

			case ImmSize.Size2:
			case ImmSize.RipRelSize2_Target16:
			case ImmSize.RipRelSize2_Target32:
			case ImmSize.RipRelSize2_Target64:
				constantOffsets.ImmediateSize = 2;
				constantOffsets.ImmediateOffset = (byte)(immAddr - eip);
				break;

			case ImmSize.Size2_1:
				constantOffsets.ImmediateSize = 2;
				constantOffsets.ImmediateOffset = (byte)(immAddr - eip);
				constantOffsets.ImmediateSize2 = 1;
				constantOffsets.ImmediateOffset2 = (byte)(immAddr - eip + 2);
				break;

			case ImmSize.Size2_2:
				constantOffsets.ImmediateSize = 2;
				constantOffsets.ImmediateOffset = (byte)(immAddr - eip);
				constantOffsets.ImmediateSize2 = 2;
				constantOffsets.ImmediateOffset2 = (byte)(immAddr - eip + 2);
				break;

			case ImmSize.Size4:
			case ImmSize.RipRelSize4_Target32:
			case ImmSize.RipRelSize4_Target64:
				constantOffsets.ImmediateSize = 4;
				constantOffsets.ImmediateOffset = (byte)(immAddr - eip);
				break;

			case ImmSize.Size4_2:
				constantOffsets.ImmediateSize = 4;
				constantOffsets.ImmediateOffset = (byte)(immAddr - eip);
				constantOffsets.ImmediateSize2 = 2;
				constantOffsets.ImmediateOffset2 = (byte)(immAddr - eip + 4);
				break;

			case ImmSize.Size8:
				constantOffsets.ImmediateSize = 8;
				constantOffsets.ImmediateOffset = (byte)(immAddr - eip);
				break;

			default:
				throw new InvalidOperationException();
			}

			return constantOffsets;
		}
	}
}
#endif
